#region Licence
// Copyright (c) 2007 James Gregory (james@jagregory.com)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Ayende Rahien nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

using System;
using System.Collections;
using System.ComponentModel;
using System.Web.UI.WebControls;

namespace JAGregory.Controls
{
	/// <summary>
	/// A GridView derived control that pages using delegates for complete control over results.
	/// </summary>
	/// <remarks>
	/// Uses delegates to perform true server-side paging, so only the records from the database that
	/// are needed for the current page are requested.
	/// </remarks>
	public class DeleGrid : GridView, IDeleGrid
	{
		private const string EventNotRaisedErrorMessage = "The DeleGrid '{0}' fired event {1} which wasn't handled.";

		private readonly IPager pager;
		private readonly ISorter sorter;
		public event PageDataRequestEventHandler PageDataRequest;
		public event TotalRecordCountRequestEventHandler TotalRecordCountRequest;

		public DeleGrid()
		{
			pager = new DeleGridPager(this);
			sorter = new DeleGridSorter(this);
		}

		public DeleGrid(IPager pager, ISorter sorter)
		{
			this.pager = pager;
			this.sorter = sorter;
		}

		/// <summary>
		/// Requests the current page of data.
		/// </summary>
		/// <returns>IEnumerable of requested data.</returns>
		protected virtual IEnumerable OnPageDataRequest(DataRequestEventArgs e)
		{
			if (PageDataRequest != null)
				return PageDataRequest(this, e);
			else
				throw new EventNotHandledException(string.Format(EventNotRaisedErrorMessage, ID, "PageDataRequest"));
		}

		/// <summary>
		/// Requests the total number of records in the complete data set.
		/// </summary>
		/// <returns>Total record count.</returns>
		protected virtual int OnTotalRecordCountRequest(DataRequestEventArgs e)
		{
			if (TotalRecordCountRequest != null)
				return TotalRecordCountRequest(this, e);
			else
				throw new EventNotHandledException(string.Format(EventNotRaisedErrorMessage, ID, "TotalRecordCountRequest"));
		}

		/// <summary>
		/// Handles sorting of the grid.
		/// </summary>
		/// <param name="e">Event arguments</param>
		protected override void OnSorting(GridViewSortEventArgs e)
		{
			sorter.Store(e.SortExpression);

			DataBind();
		}

		/// <summary>
		/// Changes the current page index and re-binds the grid.
		/// </summary>
		/// <param name="e">Event arguments</param>
		protected override void OnPageIndexChanging(GridViewPageEventArgs e)
		{
			PageIndex = e.NewPageIndex;
			DataBind();
		}

		/// <summary>
		/// Requests the data from the user and binds it to the grid.
		/// </summary>
		/// <param name="e">Event arguments</param>
		protected override void OnDataBinding(EventArgs e)
		{
			base.OnDataBinding(e);

			DataSource = OnPageDataRequest(CreateEventArgs());
		}

		/// <summary>
		/// Initialises the pager row. Requests the total record count, then sets up the row's appearance.
		/// </summary>
		/// <param name="row">Pager row</param>
		/// <param name="columnSpan">Column span of the primary pager cell</param>
		/// <param name="pagedDataSource">Data source bound to the grid</param>
		protected override void InitializePager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource)
		{
			pager.ConfigureDataSource(pagedDataSource, GetTotalRecordCount());

			base.InitializePager(row, columnSpan, pagedDataSource);
		}

		/// <summary>
		/// Creates the EventArgs for the data request process.
		/// </summary>
		/// <returns>Instance of RequestDataEventArgs.</returns>
		private DataRequestEventArgs CreateEventArgs()
		{
			return new DataRequestEventArgs(PageIndex * PageSize, PageSize, SortingField, SortingDirection); ;
		}

		/// <summary>
		/// Gets the total number of records in the data set.
		/// </summary>
		/// <remarks>
		/// Only requests the total if it hasn't previously been requested.
		/// </remarks>
		/// <returns></returns>
		private int GetTotalRecordCount()
		{
			if (TotalRecordCount <= 0 || AlwaysRequestTotal)
				TotalRecordCount = OnTotalRecordCountRequest(CreateEventArgs());

			return TotalRecordCount;
		}

		[DefaultValue(false)]
		public bool AlwaysRequestTotal
		{
			get
			{
				bool? v = ViewState[ViewStateKeys.AlwaysRequestTotal] as bool?;

				return v.HasValue ? v.Value : false;
			}
			set { ViewState[ViewStateKeys.AlwaysRequestTotal] = new bool?(value); }
		}

		/// <summary>
		/// Gets or sets the total number of records in the complete data set.
		/// </summary>
		/// <remarks>
		/// Stored in the ViewState so the query only needs to be done on initial binding.
		/// Can be reset by the user to force a re-request of the total.
		/// </remarks>
		protected int TotalRecordCount
		{
			get
			{
				int? v = ViewState[ViewStateKeys.VirtualItemCount] as int?;

				return v.HasValue ? v.Value : 0;
			}
			set { ViewState[ViewStateKeys.VirtualItemCount] = value; }
		}

		/// <summary>
		/// Gets or sets the field currently used for sorting.
		/// </summary>
		/// <remarks>
		/// Persisted in ViewState for binding use.
		/// </remarks>
		public string SortingField
		{
			get
			{
				string v = ViewState[ViewStateKeys.SortingField] as string;

				return string.IsNullOrEmpty(v) ? string.Empty : v;
			}
			set { ViewState[ViewStateKeys.SortingField] = value; }
		}

		/// <summary>
		/// Gets or sets the sorting direction currently used for sorting.
		/// </summary>
		/// <remarks>
		/// Persisted in ViewState for binding use.
		/// </remarks>
		public SortDirection SortingDirection
		{
			get
			{
				SortDirection? v = ViewState[ViewStateKeys.SortingDirection] as SortDirection?;

				return v.HasValue ? v.Value : SortDirection.Ascending;
			}
			set { ViewState[ViewStateKeys.SortingDirection] = value; }
		}

		/// <summary>
		/// Gets or sets the current page index for the grid.
		/// </summary>
		public override int PageIndex
		{
			get
			{
				int? v = ViewState[ViewStateKeys.PageIndex] as int?;

				return v.HasValue ? v.Value : 0;
			}
			set
			{
				ViewState[ViewStateKeys.PageIndex] = value;
				base.PageIndex = value;
			}
		}

		/// <summary>
		/// Constants for grid ViewState keys.
		/// </summary>
		private class ViewStateKeys
		{
			public const string AlwaysRequestTotal = "AlwaysRequestTotal";
			public const string PageIndex = "PageIndex";
			public const string SortingDirection = "SortingDirection";
			public const string SortingField = "SortingField";
			public const string VirtualItemCount = "VirtualItemCount";
		}
	}

	public delegate IEnumerable PageDataRequestEventHandler(object sender, DataRequestEventArgs e);
	public delegate int TotalRecordCountRequestEventHandler(object sender, DataRequestEventArgs e);
}